package com.bosicloud.cmb.ibp.microplatform.service.impl;


import com.amazonaws.services.ecs.model.KeyValuePair;
import com.bosicloud.cmb.ibp.aws.adapter.api.CloudFormationClient;
import com.bosicloud.cmb.ibp.aws.adapter.api.DeployAppClient;
import com.bosicloud.cmb.ibp.aws.adapter.commom.dto.*;
import com.bosicloud.cmb.ibp.commom.exception.BusinessException;
import com.bosicloud.cmb.ibp.commom.properties.AWSProperties;
import com.bosicloud.cmb.ibp.commom.utils.DateUtils;
import com.bosicloud.cmb.ibp.commom.utils.JsonUtil;
import com.bosicloud.cmb.ibp.commom.utils.RandomString;
import com.bosicloud.cmb.ibp.microplatform.commom.constant.ResultConstant;
import com.bosicloud.cmb.ibp.microplatform.commom.model.*;
import com.bosicloud.cmb.ibp.microplatform.commom.vo.CreateMicroPlatformVO;
import com.bosicloud.cmb.ibp.microplatform.commom.vo.DeployServiceVO;
import com.bosicloud.cmb.ibp.microplatform.commom.vo.EnvListVO;
import com.bosicloud.cmb.ibp.microplatform.commom.vo.ProjectInfoVO;
import com.bosicloud.cmb.ibp.microplatform.dao.*;
import com.bosicloud.cmb.ibp.microplatform.service.MicroPlatformManagerService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;


@Slf4j
@Service
public class MicroPlatformManagerServiceImpl implements MicroPlatformManagerService {


    private static final String DEPLOY_MSP_KEY = "DEPLOY-MSP";

    private final IbpMspEnvironmentMapper environmentMapper;

    private final IbpPlatformInfoMapper platformInfoMapper;

    private final IbpCfNotifyMessageMapper notifyMessageMapper;

    private final CloudFormationClient cloudFormationClient;

    private final IbpMspDeployMapper deployMapper;

    private final IbpMspEnvArgumentsMapper argumentsMapper;

    private final DeployAppClient deployAppClient;

    private final AWSProperties properties;

    private final IbpApiAwsFailtMsgMapper failtMsgMapper;

    private final IbpMspProjectInfoMapper projectInfoMapper;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Autowired
    public MicroPlatformManagerServiceImpl(IbpMspEnvironmentMapper environmentMapper,
                                           IbpPlatformInfoMapper platformInfoMapper,
                                           IbpCfNotifyMessageMapper notifyMessageMapper,
                                           IbpMspDeployMapper deployMapper,
                                           IbpMspEnvArgumentsMapper argumentsMapper,
                                           CloudFormationClient cloudFormationClient,
                                           DeployAppClient deployAppClient,
                                           AWSProperties properties,
                                           IbpApiAwsFailtMsgMapper failtMsgMapper,
                                           IbpMspProjectInfoMapper projectInfoMapper) {
        this.environmentMapper = environmentMapper;
        this.platformInfoMapper = platformInfoMapper;
        this.notifyMessageMapper = notifyMessageMapper;
        this.cloudFormationClient = cloudFormationClient;
        this.deployMapper = deployMapper;
        this.argumentsMapper = argumentsMapper;
        this.properties = properties;
        this.deployAppClient = deployAppClient;
        this.failtMsgMapper = failtMsgMapper;
        this.projectInfoMapper = projectInfoMapper;
    }

    @Override
    public List<EnvListVO> searchEnvList(Integer userId) {

        return environmentMapper.select(IbpMspEnvironment.builder()
                .createUser(userId)
                .build())
                .stream()
                .map(rs -> EnvListVO.builder()
                        .envName(rs.getEnvName())
                        .mspEnvironmentId(rs.getId())
                        .clusterName(rs.getClusterName())
                        .token(rs.getToken())
                        .status(rs.getStatus())
                        .createTime(rs.getCreateTime())
                        .build())
                .collect(Collectors.toList());
    }

    @Override
    public String createMicroServicePlatform(CreateMicroPlatformVO platformVO) {

        log.info("### Invoke Adapter Service  CreateCFStack Start... params:{}", JsonUtil.toJson(platformVO));
        CreateCFStackResponseDTO stack = cloudFormationClient.createCFStack(combinedCreateAwsEnvDTO(platformVO));
        log.info("### Invoke Adapter Service  ResponseDTO{}", JsonUtil.toJson(stack));

        IbpMspEnvironment mspEnvironment = new IbpMspEnvironment();
        BeanUtils.copyProperties(platformVO, mspEnvironment);
        mspEnvironment.setCreateTime(DateUtils.localDateTime2Date());
        mspEnvironment.setStackName(stack.getStackName());
        mspEnvironment.setAcsName("acs-" + RandomString.next(6));
        mspEnvironment.setProjectId(searchProjectInfo(IbpMspProjectInfo
                .builder()
                .createUser(platformVO.getCreateUser())
                .build())
                .map(IbpMspProjectInfo::getId)
                .orElse(0));
        environmentMapper.insert(mspEnvironment);

        return ResultConstant.SUCCESS;
    }

    private CreateAwsEnvDTO combinedCreateAwsEnvDTO(CreateMicroPlatformVO platformVO) {
        return CreateAwsEnvDTO
                .builder()
                .accessKey(platformVO.getAccessKey())
                .secretKey(platformVO.getSecretKey())
                .keyPairName(platformVO.getKeyParis())
                .token(platformVO.getToken())
                .region(platformVO.getRegionName())
                .asgMaxSize(platformVO.getEc2Count())
                .ecsInstanceType(platformVO.getEc2Type())
                .ecsClusterName(platformVO.getClusterName())
                .configGitUrl(platformVO.getGitUrl())
                .configGitUser(platformVO.getGitAccount())
                .configGitPassword(platformVO.getGitPassword())
                .ecsSecurityGroup(platformVO.getEcsSafetyGroupId())
                .lbSecurityGroup(platformVO.getLbSafetyGroupId())
                .vpnId(platformVO.getVpcId())
                .subnetIds(platformVO.getPrivateSubNetwork())
                .ecsRole(platformVO.getEcsRole())
                .ec2Role(platformVO.getEc2Role())
                .s3template(properties.getS3cloudformationTemplate())
                .build();
    }

    @Override
    public IbpPlatformInfo searchPlatformInfoByEnvironmentId(Integer mspEnvironmentId, String envName) {

        return platformInfoMapper.selectOne(IbpPlatformInfo.builder()
                .mspEnvironmentId(mspEnvironmentId)
                .envName(envName)
                .build());
    }

    @Override
    public IbpMspEnvironment searchMspEnvironment(String envName, String clusterName) {
        return environmentMapper.selectOne(IbpMspEnvironment.builder()
                .envName(envName)
                .clusterName(clusterName)
                .build());
    }

    @Override
    public List<IbpCfNotifyMessage> searchCfNotifyMessage(String token) {
        return notifyMessageMapper.select(IbpCfNotifyMessage
                .builder()
                .clientRequestToken(token)
                .build())
                .stream()
                .sorted(Comparator.comparing(IbpCfNotifyMessage::getAwsTimestamp))
                .collect(Collectors.toList());
    }

    @Override
    public IbpMspEnvironment searchMspEnvironmentByToken(String token) {
        return environmentMapper.selectOne(IbpMspEnvironment.builder()
                .token(token)
                .build());
    }

    @Override
    public String deployService(DeployServiceVO deployVO) {

        log.info("### Deploy Service Start... params:{}", JsonUtil.toJson(deployVO));

        IbpMspEnvironment mspEnvironment = this.searchMspEnvironment(deployVO.getEnvName()
                , deployVO.getClusterName());

        Optional.ofNullable(mspEnvironment)
                .orElseThrow(() -> new BusinessException("当前环境和集群名称不存在,请先创建微服务."));

        IbpMspDeploy deploy = deployMapper.selectOne(IbpMspDeploy
                .builder()
                .clusterName(deployVO.getClusterName())
                .envName(deployVO.getEnvName())
                .serviceName(deployVO.getServiceName())
                .build());

        if (Objects.nonNull(deploy)) {
            throw new BusinessException("当前环境下不支持重复部署同一个应用.");
        }

        IbpPlatformInfo platformInfo = platformInfoMapper.selectOne(IbpPlatformInfo
                .builder()
                .mspEnvironmentId(mspEnvironment.getId())
                .build());
        if (Objects.isNull(platformInfo)) {
            throw new BusinessException("该微服务平台信息不存在.");
        }


        IbpMspDeploy mspDeploy = new IbpMspDeploy();
        BeanUtils.copyProperties(deployVO, mspDeploy);
        mspDeploy.setCreateTime(DateUtils.localDateTime2Date());
        mspDeploy.setEnvironmentId(mspEnvironment.getId());
        mspDeploy.setAwsTaskName("atn-" + RandomString.next(6));
        deployMapper.insert(mspDeploy);
        Integer deployId = mspDeploy.getId();
        log.info("### Save IbpMspDeploy success, return generatedValue:{}", deployId);

        deployVO.getArgumentsList()
                .forEach(rs -> argumentsMapper.insert(IbpMspEnvArguments.builder()
                        .argumentsKey(rs.getArgumentsKey())
                        .argumentsValue(rs.getArgumentsValue())
                        .deployId(mspDeploy.getId())
                        .createTime(DateUtils.localDateTime2Date())
                        .build()));
        DeployeAppResponseDTO responseDTO;
        DeployeAppParamDTO deployeAppParamDTO = combinedDeployParamDTO(deployVO, mspEnvironment, platformInfo);
        try {
            log.info("### Invoke Adapter Service deployeApps Start... params:{}", JsonUtil.toJson(deployeAppParamDTO));
            responseDTO = deployAppClient.deployeApps(deployeAppParamDTO);
            log.info("### Invoke Adapter Service  ResponseDTO{}", JsonUtil.toJson(responseDTO));
        } catch (Exception e) {
            log.error("### Invoke Adapter Service Error:{}", e.getMessage(), e);
            recordFailMessage(deployVO.getServiceName(), deployId, e.getMessage());
            Integer updateMspDeploy = deployMapper.updateMspDeploy(IbpMspDeploy.builder()
                    .id(deployId)
                    .deployStatus(ResultConstant.FAIL)
                    .build());
            log.info("### Update MspDeploy status fail record:{}", updateMspDeploy);
            throw e;
        }

        mspDeploy.setServiceArn(responseDTO.getServiceArn());
        mspDeploy.setServiceUrl(responseDTO.getElbUrl());
        mspDeploy.setDeployStatus(ResultConstant.SUCCESS);
        Integer updateMspDeploy = deployMapper.updateMspDeploy(mspDeploy);
        log.info("### Update MspDeploy record:{}", updateMspDeploy);

        return ResultConstant.SUCCESS;
    }

    private void recordFailMessage(String serviceName, Integer deployId, String message) {
        try {
            failtMsgMapper.insert(IbpApiAwsFailtMsg.builder()
                    .apiId(deployId)
                    .bizType(DEPLOY_MSP_KEY)
                    .context(serviceName)
                    .msg(message)
                    .createTime(DateUtils.localDateTime2Date())
                    .build());
        } catch (Exception e) {
            log.error("### Record Fail Message Error:{}", e.getMessage(), e);
        }
    }

    private DeployeAppParamDTO combinedDeployParamDTO(DeployServiceVO deployVO,
                                                      IbpMspEnvironment mspEnvironment,
                                                      IbpPlatformInfo platformInfo) {

        List<KeyValuePair> keyValuePairs = deployVO.getArgumentsList()
                .stream()
                .filter(rs -> StringUtils.isNotEmpty(rs.getArgumentsKey()))
                .map(envArguments -> {
                    KeyValuePair keyValuePair = new KeyValuePair();
                    keyValuePair.setName(envArguments.getArgumentsKey());
                    keyValuePair.setValue(envArguments.getArgumentsValue());
                    return keyValuePair;
                })
                .collect(Collectors.toList());

        return DeployeAppParamDTO.builder()
                .accessKey(mspEnvironment.getAccessKey())
                .secretKey(mspEnvironment.getSecretKey())
                .region(mspEnvironment.getRegionName())
                .vpcId(mspEnvironment.getVpcId())
                .ecsRoleArn(mspEnvironment.getEcsRole())
                .ecsClusterName(mspEnvironment.getClusterName())
                .taskName(deployVO.getServiceName())
                .memory(deployVO.getContainerMemory())
                .cpu(deployVO.getContainerCpu())
                .image(deployVO.getDockerImagePath())
                .checkHealthPath(deployVO.getHealthPath())
                .elbArn(deployVO.getServiceType().equalsIgnoreCase("open")
                        ? platformInfo.getPalbArn()
                        : platformInfo.getAlbArn())
                .containerPort(deployVO.getContainerPort())
                .elbPort(deployVO.getListenerPort())
                .instanceNum(deployVO.getExpectInstanceCount())
                .healthCheckTimeout(deployVO.getHealthQueryDelay())
                .deployPath(deployVO.getDeployPath())
                .taskRoleArn(deployVO.getTaskRole())
                .environment(keyValuePairs)
                .logGroup(platformInfo.getEcsLogGroup())
                .build();
    }

    @Override
    public IbpMspDeploy searchMspDeploy(IbpMspDeploy deploy) {
        return deployMapper.selectOne(deploy);
    }

    @Override
    public List<IbpMspDeploy> searchMspDeployListByEnvironmentId(Integer mspEnvironmentId) {

        List<IbpMspDeploy> deployList = deployMapper.select(IbpMspDeploy
                .builder()
                .environmentId(mspEnvironmentId)
                .build());
        if (CollectionUtils.isEmpty(deployList)) {
            return new ArrayList<>();
        }

        List<String> serviceNameList = deployList
                .stream()
                .map(IbpMspDeploy::getServiceName)
                .collect(Collectors.toList());

        GetServiceInfoDTO serviceInfoDTO = combinedServiceInfoDTO(mspEnvironmentId, serviceNameList);
        log.info("### Combined GetServiceInfoDTO:{}", JsonUtil.toJson(serviceInfoDTO));

        Map<String, Integer> serviceMap = deployAppClient.getServiceInfo(serviceInfoDTO)
                .stream()
                .collect(Collectors.toMap(com.amazonaws.services.ecs.model.Service::getServiceArn,
                        com.amazonaws.services.ecs.model.Service::getRunningCount));
        return deployList.stream()
                .peek(rs -> rs.setRunInstanceCount(serviceMap.get(rs.getServiceArn())))
                .collect(Collectors.toList());
    }

    private GetServiceInfoDTO combinedServiceInfoDTO(Integer mspEnvironmentId, List<String> serviceNameList) {

        IbpMspEnvironment mspEnvironment = environmentMapper.selectOne(IbpMspEnvironment
                .builder()
                .id(mspEnvironmentId)
                .build());

        return GetServiceInfoDTO
                .builder()
                .accessKey(mspEnvironment.getAccessKey())
                .secretKey(mspEnvironment.getSecretKey())
                .region(mspEnvironment.getRegionName())
                .clusterName(mspEnvironment.getClusterName())
                .serviceNames(serviceNameList)
                .build();
    }

    @Override
    public IbpApiAwsFailtMsg searchFailMsg(Integer deployId, String taskName) {
        return failtMsgMapper.selectOne(IbpApiAwsFailtMsg.builder()
                .apiId(deployId)
                .context(taskName)
                .bizType(DEPLOY_MSP_KEY)
                .build());
    }

    @Override
    public String saveProjectInfo(ProjectInfoVO projectInfoVO) {


        Optional<IbpMspProjectInfo> optionalInfo = searchProjectInfo(IbpMspProjectInfo
                .builder()
                .createUser(projectInfoVO.getCreateUser())
                .projectNum(projectInfoVO.getProjectNum())
                .projectName(projectInfoVO.getProjectName())
                .build());
        try {
            if (!optionalInfo.isPresent()) {
                int insert = projectInfoMapper.insert(IbpMspProjectInfo
                        .builder()
                        .projectName(projectInfoVO.getProjectName())
                        .projectNum(projectInfoVO.getProjectNum())
                        .createUser(projectInfoVO.getCreateUser())
                        .createTime(DateUtils.localDateTime2Date())
                        .build());
                return insert > 0 ? ResultConstant.SUCCESS : ResultConstant.FAIL;
            }
        } catch (DuplicateKeyException e) {
            throw new BusinessException("当前用户的项目信息已存在,请不要重复添加");
        }
        return ResultConstant.FAIL;

    }

    @Override
    public Optional<IbpMspProjectInfo> searchProjectInfo(IbpMspProjectInfo projectInfo) {
        return Optional.ofNullable(projectInfoMapper.selectOne(projectInfo));
    }
}
